/*
 * busconsole.c
 *
 *  Created on: Feb 17, 2011
 *      Author: s126135
 */

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/errno.h>
#include <sys/ioctl.h>
#include <pthread.h>

#include "../mod_general.h"
#include "../terminals.h"
#include "../list.h"

#define PRESENTATION "\e[1;34m * Hello. This is casperd daemon.\n\e[1;34m * Type help for list of available commands.\e[0m"

int sock = -1;

list* socks;

list* out_of_transaction;

void manage_transaction(terminal_t* daemon, terminal_t* terminal) {
	int in_transaction = 0;
	char* string;
	
	do {
		if (in_transaction)
			free(string);
		string = terminal_read_from_daemon (daemon);
		if (!strcmp(string, "transaction begin"))
			in_transaction = 1;
		if (in_transaction == 0)
			list_add(out_of_transaction, string, 0);
		else
			if (terminal_write_to_terminal (terminal, string) <= 0 && strlen(string) > 0) {
				free(string);
				return;
			}
	} while (strcmp(string, "transaction end"));
	free(string);
}

void* casper_console(void* arg) {
	terminal_t* term = (terminal_t*) arg;
	terminal_t* usr;
	fd_set fds;
	int res, conn_sock, max, *ptr, jump_select = 0;
	char buffer[4096];
	char* string;
	struct sockaddr_un peer_addr;
	socklen_t peer_addr_size;

	buffer[4095] = 0;

	peer_addr_size = sizeof(struct sockaddr_un);

	while (1) {
		fprintf(stderr, "DEBUG: busconsole: managing %d clients\n", list_size(socks));
		
		max = term->pins.terminal_read;

		jump_select = 0;

		for (res = list_size(socks) - 1; res >= 0; res--) {
			usr = (terminal_t*) list_get(socks, res);
			if (usr == NULL) {
				fprintf(stderr, "BUG: busconsole: list_get(socks, %d) returns null\n", res);
				continue;
			}
			if (terminal_has_more_for_daemon (usr))
				jump_select = 1;
		}
		if (terminal_has_more_for_terminal (term))
			jump_select = 1;

		FD_ZERO(&fds);
		if (!jump_select) {
			FD_SET(sock, &fds);
			FD_SET(term->pins.terminal_read, &fds);
	
			if (sock > max)
				max = sock;
	
			res = list_size(socks) - 1;
			for (; res >= 0; res--) {
				usr = (terminal_t*) list_get(socks, res);
				if (usr == NULL) {
					fprintf(stderr, "BUG: busconsole: element %d not found in list \"socks\" (size: %d)\n", res, list_size(socks));
					continue;
				}
				FD_SET(usr->pins.daemon_read, &fds);
				if (usr->pins.daemon_read > max)
					max = usr->pins.daemon_read;
			}
			res = select (sizeof(fds)*8, &fds, NULL, NULL, NULL);
			if (res == -1) {
				fprintf(stderr, "ERROR: busconsole: select() failed: exiting...\n");
				fprintf(stderr, "                   -> %s\n", strerror(errno));
				for (max = 0; max < list_size(socks);) {
					usr = list_get(socks, 0);
					close(usr->pins.daemon_read);
					terminal_free (usr);
					if (list_remove(socks, 0) == NULL)
						fprintf(stderr, "BUG: busconsole: removed nothing from list\n");
				}
				continue;
			}
		}

		// Accepting new connections
		if (FD_ISSET(sock, &fds)) {
			conn_sock = accept(sock, (struct sockaddr *) &peer_addr, &peer_addr_size);
			if (conn_sock <= 0)
				continue;

			fprintf(stderr, "INFO: busconsole: client connected (%d)\n", conn_sock);

			if (write(conn_sock, PRESENTATION, strlen(PRESENTATION) + 1) <= 0)
				continue;

			usr = terminal_new_socket(conn_sock);
			list_add(socks, usr, conn_sock);
		}
		// Data from daemon to clients
		if (terminal_has_more_for_terminal(term) || FD_ISSET(term->pins.terminal_read, &fds)) {
			do {
				string = terminal_read_from_daemon(term);
				if (string == NULL)
					return NULL;
				if (strlen(string) == 0) {
					free(string);
					continue;
				}
				max = list_size(socks) - 1;
				for (; max >= 0; max--) {
					usr = (terminal_t*) list_get(socks, max);
					if (usr == NULL) {
						fprintf(stderr, "BUG: busconsole: list_get returned NULL from list \"socks\" with index %d\n", max);
						continue;
					}
					if (terminal_write_to_terminal(usr, string) <= 0 && strlen(string) > 0) {
						terminal_free(list_remove (socks, max));
					}
				}
				free(string);
			} while (terminal_has_more_for_terminal(term));
		}
		// Data from clients to daemon
		for (max = list_size(socks) - 1; max >= 0; max--) {
			usr = (terminal_t*) list_get(socks, max);
			if (usr == NULL) {
				fprintf(stderr, "BUG: busconsole: list_get(socks, %d) returned NULL\n", max);
				continue;
			}
			if (!terminal_has_more_for_daemon(usr) && !FD_ISSET(usr->pins.daemon_read, &fds))
				continue;
			do {
				string = terminal_read_from_terminal(usr);
				if (string == NULL) {
					fprintf(stderr, "INFO: busconsole: client disconnected (%d)\n", usr->pins.daemon_read);
					break;
				}
				terminal_write_to_daemon(term, string);
				if (!strncmp(string, "transaction ", 12))
					manage_transaction(term, usr);
				free(string);
			} while (string != NULL && terminal_has_more_for_daemon(usr));
			if (string == NULL) {
				//close(usr->pins.daemon_read);
				if (list_remove_key(socks, usr->pins.daemon_read) == NULL)
					fprintf(stderr, "BUG: busconsole: unable to find socket to be removed\n");
				else
					terminal_free(usr);
			}
		}
		// Data out of transaction from daemon to clients
		while(list_size (out_of_transaction) > 0) {
			string = (char*) list_remove(out_of_transaction, 0);
			if (strlen(string) == 0) {
				free(string);
				continue;
			}
			max = list_size(socks) - 1;
			for (; max >= 0; max--) {
				usr = (terminal_t*) list_get(socks, max);
				if (usr == NULL) {
					fprintf(stderr, "BUG: busconsole: list_get returned NULL from \"socks\" with index %d\n", max);
					continue;
				}
				if (terminal_write_to_terminal(usr, string) <= 0 && strlen(string) > 0) {
					terminal_free(list_remove (socks, max));
				}
			}
			free(string);
		}
	}

}

int module_init(terminal_t* term) {
	pthread_t tid;
	int res;
	char* str;
	struct sockaddr_un my_addr;

	socks = list_new();
	out_of_transaction = list_new();

	str = getenv("CASPER_BUSCONSOLE_SOCKET");
	if (str == NULL) {
		fprintf(stderr, "ERROR: busconsole: unable to find CASPER_BUSCONSOLE_SOCKET variable\n");
		return 1;
	}

	memset(&my_addr, 0, sizeof(struct sockaddr_un));

	if (sock == -1) {
		sock = socket(AF_UNIX, SOCK_STREAM, 0);
		if (sock == -1) {
			fprintf(stderr, "ERROR: busconsole: socket() failed: unable to create local UNIX socket (errno: %d)\n", errno);
			return errno;
		}
	}

	my_addr.sun_family = AF_UNIX;
	strncpy(my_addr.sun_path, str, sizeof(my_addr.sun_path) - 1);

	fprintf(stderr, "INFO: busconsole: trying to open socket %s\n", str);
	if (bind(sock, (struct sockaddr *) &my_addr, sizeof(struct sockaddr_un)) == -1) {
		fprintf(stderr, "ERROR: busconsole: bind() failed: unable to bind local UNIX socket (errno: %d)\n", errno);
		return 1;
	}

	listen(sock, 50);

	fprintf(stderr, "INFO: busconsole: unix local socket created: %s\n", str);

	res = pthread_create(&tid, NULL, casper_console, term);
	if (res != 0) {
		fprintf(stderr, "ERROR: busconsole: unable to create thread (pthread_create: %d)\n", res);
		return errno;
	}
	fprintf(stderr, "INFO: busconsole: created console server thread\n");

	return 0;
}
